import sys, os, math

from PyQt4.QtCore import QObject, SIGNAL, QPointF, QLineF, QRectF, Qt, QEvent
from PyQt4.QtGui import QGraphicsItem, QGraphicsView, QGraphicsScene, QWidget, QHBoxLayout, QLabel, \
                        QGraphicsProxyWidget, QGraphicsLineItem, QToolButton, QGraphicsEllipseItem, \
                        QGraphicsObject, QGraphicsLayoutItem, QGraphicsRectItem, QGraphicsTextItem, \
                        QAction, QToolButton, QHBoxLayout, QWidget, QCursor, QKeySequence, QColor, QPen

from core import cmds

from ui.widgets import PipelineComboBox

class Tool(object):
    def __init__(self, scene):
        self.scene = scene
        
    def exit_tool(self):
        self.scene.deactivate_tool()


class ConnectTool(Tool):
    def __init__(self, scene, start_item):
        Tool.__init__(self, scene)
        self.start_item = start_item
        self.end_item = None
        self.edge = QGraphicsLineItem()
        self.scene.addItem(self.edge)
        
    def on_mouse_move(self, event):
        self.edge.setLine(QLineF(self.start_item.sceneBoundingRect().center(), event.scenePos()))
        self.edge.setZValue(-1.0)
        
    def on_mouse_release(self, event):
        self.end_item = self.scene.items(event.scenePos())
        
        if len(self.end_item) > 0:
            self.end_item = self.end_item[0]
        else:
            self.end_item = None
        
        try:
            if self.end_item is not None and isinstance(self.end_item, AttrItem):
                self.scene.connect_items(self.start_item, self.end_item)
        except:
            raise
        finally:
            self.scene.removeItem(self.edge)
            self.scene.reset()
            self.exit_tool()


class EdgeItem(QGraphicsLineItem):
    focus_color = QColor(255,0,0)
    color = QColor(0,0,0)
    
    def __init__(self, start_item, end_item):
        QGraphicsLineItem.__init__(self)
        self.start_item = start_item
        self.end_item = end_item
        self.setAcceptHoverEvents(True)
        self.setAcceptsHoverEvents(True)
        self.setFlags(QGraphicsItem.ItemIsFocusable)
        pen = self.pen()
        pen.setWidth(2)
        self.setPen(pen)
        self.update_position()

    def remove(self, pipeline):
        pipeline.remove_edge(self.start_item.component(), self.start_item.attr_name(),
                             self.end_item.component(), self.end_item.attr_name())

    def update_position(self):
        self.setLine(QLineF(self.start_item.sceneBoundingRect().center(), self.end_item.sceneBoundingRect().center()))
        self.setZValue(-1.0)

    def focusInEvent(self, event):
        pen = self.pen()
        pen.setColor(self.focus_color)
        self.setPen(pen)
        QGraphicsLineItem.focusInEvent(self, event)

    def focusOutEvent(self, event):
        pen = self.pen()
        pen.setColor(self.color)
        self.setPen(pen)
        QGraphicsLineItem.focusOutEvent(self, event)

    def hoverEnterEvent(self, event):
        self.scene().itemHoverEnter(self)
        QGraphicsLineItem.hoverEnterEvent(self, event)
    
    def hoverLeaveEvent(self, event):
        self.scene().itemHoverLeave(self)
        QGraphicsLineItem.hoverLeaveEvent(self, event)

class AttrItem(QGraphicsEllipseItem):
    def __init__(self, parent, handle, radius=5.0):
        QGraphicsEllipseItem.__init__(self, parent)
        self._radius = radius
        self._handle = handle
        self._name_text = QGraphicsTextItem(self)
        self._name_text.setVisible(False)
        
        self.setToolTip(self._handle.attr.name)
        
        self.setAcceptHoverEvents(True)
        self.setAcceptsHoverEvents(True)
        self.setRect(QRectF(-radius,-radius, radius*2.0, radius*2.0))
        
    def component(self): return self._handle.component
    
    def attr_name(self): return self._handle.attr.name
        
    def show_name(self): return
    def hide_name(self): return
        
    def mousePressEvent(self, event):
        self.scene().activate_tool(ConnectTool(self.scene(), self))
        
    def hoverEnterEvent(self, event):
        self.scene().itemHoverEnter(self)
        self.show_name()
        QGraphicsEllipseItem.hoverEnterEvent(self, event)
    
    def hoverLeaveEvent(self, event):
        self.scene().itemHoverLeave(self)
        self.hide_name()
        QGraphicsEllipseItem.hoverLeaveEvent(self, event)
        
class ArcLayout(QGraphicsRectItem):
    def __init__(self, parent, radius=20, space=50, start=180):
        QGraphicsRectItem.__init__(self, parent)
        self.radius = radius
        self.space = space
        self.start = start
        
    def add_items(self, *items):
        for item in items:
            item.setParentItem(self)
        self.update_layout()
            
    def update_layout(self):
        items = self.childItems()
        num_items = len(items)
        length = math.radians(self.space)*num_items-1
        space_rad = math.radians(self.space)
        curr_rad = math.radians(self.start) - length/2.0
        for item in items:
            
            x = math.cos(curr_rad) * self.radius
            y = math.sin(curr_rad) * self.radius
            item.setPos(x,y)
            curr_rad += space_rad

class AttrListItem(QGraphicsRectItem):
    def __init__(self, parent):
        QGraphicsRectItem.__init__(self, parent)
        self.layout = ArcLayout(self)
        
    def add_attrs(self, component, attrs):
        for attr in attrs:
            handle = component.handle(attr)
            attr_item = AttrItem(self, handle)
            self.layout.add_items(attr_item)
    
    def attr_item(self, attr):
        for child_item in self.layout.childItems():
            if child_item._handle.attr is attr:
                return child_item

class NodeWidget(QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self._layout = QHBoxLayout()
        self._layout.setMargin(0)
        self._layout.setSpacing(0)
        
        self.label = QLabel(self)
        
        
        self._layout.addWidget(self.label)
        
        self.setLayout(self._layout)

    

    def set_text(self, text):
        self.label.setText(str(text))

class NodeProxyWidget(QGraphicsProxyWidget):
    def __init__(self, parent, node, widget):
        QGraphicsProxyWidget.__init__(self, parent)
        self.setFlags(QGraphicsItem.ItemSendsGeometryChanges | QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsFocusable)
        self._node = node
        self._widget = widget
        self._widget.set_text(self._node.name)
        self.setWidget(widget)
        
        rect = self.boundingRect()
        
        self.out_attrs_item = AttrListItem(self)
        self.out_attrs_item.layout.start = 0
        self.out_attrs_item.add_attrs(self._node, self._node.output_attrs())
        self.out_attrs_item.setPos(rect.right(), rect.center().y())
        
        self.in_attrs_item = AttrListItem(self)
        self.in_attrs_item.add_attrs(self._node, self._node.input_attrs())
        self.in_attrs_item.setPos(rect.left(), rect.center().y())
        
        self.setAcceptHoverEvents(True)
        self.setAcceptsHoverEvents(True)
        
        
    def focusInEvent(self, event):
        QGraphicsProxyWidget.focusInEvent(self, event)
        
    def focusOutEvent(self, event):
        QGraphicsProxyWidget.focusOutEvent(self, event)
    
    def itemChange(self, change, value):
        if not self.scene():
            return QGraphicsItem.itemChange(self, change, value)
        
        if change == QGraphicsItem.ItemPositionChange:
            self.scene().update_edge_positions()
            
        return QGraphicsItem.itemChange(self, change, value)
        
    def attr_item(self, attr):
        for child in self.childItems():
            if isinstance(child, AttrListItem):
                attr_item = child.attr_item(attr)
                if not attr_item is None:
                    return attr_item
        
    def hoverEnterEvent(self, event):
        self.scene().itemHoverEnter(self)
        QGraphicsProxyWidget.hoverEnterEvent(self, event)
    
    def hoverLeaveEvent(self, event):
        self.scene().itemHoverLeave(self)
        QGraphicsProxyWidget.hoverLeaveEvent(self, event)
        
    def mouseDoubleClickEvent (self, event):
        return QGraphicsItem.mouseDoubleClickEvent(self, event)
    
    def mousePressEvent (self, event):
        if event.button() == Qt.RightButton:
            obj = self._node
            menu = cmds.action_menu(obj, None)
            if menu is None: return
            menu.popup(QCursor.pos())
            
        else:
            return QGraphicsItem.mousePressEvent(self, event)
    
    
    def mouseReleaseEvent (self, event):
        return QGraphicsItem.mouseReleaseEvent(self, event)
    
    def mouseMoveEvent (self, event):
        return QGraphicsItem.mouseMoveEvent(self, event)

class GraphView(QGraphicsView):
    def __init__(self,parent=None):
        QGraphicsView.__init__(self, parent)
        self.setObjectName("Graph")
        self._scene = GraphScene(self)
        self.setScene(self._scene)
        self.centerOn(1000,1000)
        self.setInteractive(True)
        self.setDragMode(QGraphicsView.RubberBandDrag)
        self.setAcceptDrops(True)
        
        
        self.button_widget = QWidget(self)
        hbox = QHBoxLayout()
        self.button_widget.setLayout(hbox)
        
        self.run_act = QAction(self)
        self.run_act.setText("Update")
        self.connect(self.run_act, SIGNAL("triggered()"), self.update_pipeline)
        
        self.delete_act = QAction(self)
        self.delete_act.setShortcut(QKeySequence.Delete)
        self.delete_act.setShortcutContext(Qt.WidgetShortcut)
        self.delete_act.setText("Delete")
        self.connect(self.delete_act, SIGNAL("triggered()"), self.scene().delete_item)
        
        self.addAction(self.delete_act)
        self.addAction(self.run_act)
        
        self.run_btn = QToolButton(self)
        self.run_btn.setDefaultAction(self.run_act)
        
        self.pipeline_cb = PipelineComboBox(self)
        
        
        hbox.addWidget(self.pipeline_cb)
        hbox.addSpacing(1)
        hbox.addWidget(self.run_btn)
        
        
        #self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
        
    def update_pipeline(self):
        self.scene().pipeline.update()
        
    def dragEnterEvent(self, event):
        
        mimedata = event.mimeData()
        if not mimedata.hasFormat('text/plain'):
            event.ignore()
        
        if event.source() == self:
            event.setDropAction(QtCore.Qt.MoveAction)
            event.accept()
        else:
            event.acceptProposedAction()

    dragMoveEvent = dragEnterEvent
    
        
    def dropEvent(self, event):
        mimedata = event.mimeData()
        data = cmds.object_from_string(str(mimedata.text()))
        if data is None:
            return
        
        if cmds.is_type(data):
            data = data()
        
        self.scene().pipeline.add_node(data)
        self.scene().reset()
        return QGraphicsView.dropEvent(self, event)
        
    def set_pipeline(self, pipe):
        self.scene().pipeline = pipe
        
    def reset(self): 
        self.scene().reset()
        self.pipeline_cb.refresh()
        
    def clear(self): 
        self.scene().clear()
        
    def keyReleaseEvent(self, event):
      if event.key() == 16777251:
        self.setDragMode(QGraphicsView.RubberBandDrag)

    def keyPressEvent(self, event):
        if event.key() == 16777251:
            self.setDragMode(QGraphicsView.ScrollHandDrag)
        if event.text() == '+':
            self.scale(1.1,1.1)
        if event.text() == '-':
        	self.scale(0.9,0.9)

class GraphScene(QGraphicsScene):
    
    def __init__(self,parent=None):
        QGraphicsScene.__init__(self, parent)
        self.setObjectName("GraphScene")
        self._canvas_size = 2000
        self._pipeline = None
        self._node_widget = NodeWidget
        self._active_tool = None
        
        
    def delete_item(self):
        focus_item = self.focusItem()
        if focus_item is None: return
        
        if isinstance(focus_item, NodeProxyWidget):
            self.remove_node_item(focus_item)
            
        elif isinstance(focus_item, EdgeItem):
            self.remove_edge_item(focus_item)
    #def focusInEvent (self, event):
    #    print 'FOCUS IN', event, dir(event)
        
    #def focusOutEvent(self, event):
    #    print 'FOCUS OUT', event, dir(event)
        
    #def event(self, event):
    #    if event.type() == QEvent.GraphicsSceneHoverEnter:
    #        print 'HOVER', event, dir(event)
    #    return QGraphicsScene.event(self, event)
        
    
        
    def itemHoverEnter(self, item):
        self.setFocusItem(item)
        if not self._active_tool is None and hasattr(self._active_tool, 'on_item_enter'):
            self._active_tool.on_item_enter(item)
            
    def itemHoverLeave(self, item):
        self.clearFocus()
        if not self._active_tool is None and hasattr(self._active_tool, 'on_item_leave'):
            self._active_tool.on_item_leave(item)
        
    def activate_tool(self, tool):
        if not isinstance(tool, Tool):
            return
        self._active_tool = tool
        
    def deactivate_tool(self):
        self._active_tool = None
    
    def mouseMoveEvent(self, event):
        if not self._active_tool is None and hasattr(self._active_tool, 'on_mouse_move'):
            self._active_tool.on_mouse_move(event)
        return QGraphicsScene.mouseMoveEvent(self, event)
    
    def mouseReleaseEvent(self, event):
        if not self._active_tool is None and hasattr(self._active_tool, 'on_mouse_release'):
            self._active_tool.on_mouse_release(event)
        return QGraphicsScene.mouseReleaseEvent(self, event)
        
    
    @apply
    def canvas_size():
        def set_canvas_size(self, size):
            self._canvas_size = size
            self._update_rect()
        def get_canvas_size(self): 
            return self._canvas_size    
        return property(get_canvas_size, set_canvas_size) 
	
    @apply
    def pipeline():
        def get(self):
            return self._pipeline
        def set(self, pipe):
            self._pipeline = pipe
            self.reset()
        return property(get, set)
    
    def current_component(self):
        sel_items = self.selectedItems()
        if len(sel_items) > 0:
            return sel_items[-1]._node
    
    def update_edge_positions(self):
        for edge in self.edge_items():
            edge.update_position()
            
    def edge_items(self):
        for item in self.items():
            if isinstance(item, EdgeItem):
                yield item
    
    def _update_rect(self):
        self.setSceneRect(-self.canvas_size,-self.canvas-size,
                          self.canvas_size*2,self.canvas_size*2)
  
    def item_from_node(self, node):
        for item in self.items():
            if not hasattr(item, '_node'): continue
            if item._node == node:
                return item
  
    def layout_nodes(self, layout_dict):
        for node, pos in layout_dict.iteritems():
            item = self.item_from_node(node)
            item.setPos(pos[0], pos[1])
  
    def reset(self):
        self.clear()
        if self.pipeline is None: return
        
        for node in self.pipeline.nodes():
            self.add_node(node)
            
        for out_node, out_attr, in_node, in_attr in self.pipeline.edges(attrs=True):
            self.add_edge(out_node, out_attr, in_node, in_attr)
         
        #self.pipeline.tree_layout()   
        self.layout_nodes(self.pipeline.tree_layout())
        
        self.update_edge_positions()
    
    def connect_items(self, start_item, end_item):
        self.pipeline.connect(start_item.component(), start_item.attr_name(),
                              end_item.component(), end_item.attr_name())
           
    def add_edge(self, out_node, out_attr, in_node, in_attr):
        out_item = self.item_from_node(out_node)
        in_item = self.item_from_node(in_node)
        out_attr_item = out_item.attr_item(out_attr)
        in_attr_item = in_item.attr_item(in_attr)
        if in_attr_item is None or out_attr_item is None: return
        self.addItem(EdgeItem(out_attr_item, in_attr_item))
     
    def add_node(self, node):
        widget = NodeWidget()
        proxy = NodeProxyWidget(None, node, widget)
        self.addItem(proxy)
        return proxy
  
    def remove_node_item(self, item):
        self.pipeline.remove_node(item._node)
        self.reset()
        
    def remove_edge_item(self, item):
        item.remove(self.pipeline)
        self.reset()
        
        

  
        
        

  